package com.landon.redpackage.api.red.facade.impl;

import com.landon.redpackage.api.red.dto.RedPackageDto;
import com.landon.redpackage.api.red.dto.RobRedPackageDto;
import com.landon.redpackage.api.red.facade.RedPackageFacade;
import com.landon.redpackage.base.utils.BeanUtils;
import com.landon.redpackage.module.red.entity.RedDetailEntity;
import com.landon.redpackage.module.red.entity.RedRecordEntity;
import com.landon.redpackage.module.red.entity.RedRobRecordEntity;
import com.landon.redpackage.module.red.service.RedDetailService;
import com.landon.redpackage.module.red.service.RedRecordService;
import com.landon.redpackage.module.red.service.RedRobRecordService;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author ：shishaopeng
 * @date ：Created in 2021/5/3 9:33 下午
 * @description：
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class RedPackageFacadeImpl implements RedPackageFacade {

    private static final Logger logger = LoggerFactory.getLogger(RedPackageFacadeImpl.class);
    private static final String KEY_PREFIX = "redis:red:packet:";
    private final RedisTemplate redisTemplate;
    private final RedDetailService redDetailService;
    private final RedRecordService redRecordService;
    private final RedRobRecordService redRobRecordService;

    public RedPackageFacadeImpl(RedisTemplate redisTemplate, RedDetailService redDetailService, RedRecordService redRecordService, RedRobRecordService redRobRecordService) {
        this.redisTemplate = redisTemplate;
        this.redDetailService = redDetailService;
        this.redRecordService = redRecordService;
        this.redRobRecordService = redRobRecordService;
    }

    @Override
    public String handOutRedPackage(RedPackageDto redPackageDto) {
        Integer amount = redPackageDto.getAmount();
        Integer total = redPackageDto.getTotal();
        Long userId = redPackageDto.getUserId();

        boolean isNull = amount < 0 && total < 0;
        if (isNull) {
            logger.error("参数校验异常 redPackageDto={}", redPackageDto);
            return null;
        }
        List<Integer> result = this.delverRedPackage(amount, total);
        // 生成红包全局唯一字符串
        String uuid = String.valueOf(System.nanoTime());
        String newKey = KEY_PREFIX + userId + ":" + uuid;
        // 防止序列化产生问题
        List<String> target = new ArrayList<>();
        result.forEach(it -> target.add(it.toString()));
        redisTemplate.opsForList().leftPushAll(newKey, target);

        // 红包总数放入缓存
        String redTotalKey = newKey + ":total";
        redisTemplate.opsForValue().set(redTotalKey, total.toString());
        logger.info("已将生成的红包金额放入缓存；{}", target);
        // 记录红包相关信息至数据库
        this.saveRedPackageRelatedInfo(redPackageDto, newKey, result);
        return newKey;
    }

    @Override
    public BigDecimal robRedPackage(String newKey, Integer userId) {
        boolean isNullByKeyParam = Strings.isBlank(newKey) && userId == null;
        if (isNullByKeyParam) {
            logger.error("参数校验异常 newKey={}, userId={}", newKey, userId);
            return null;
        }
        // 先判断当前用户有没有抢过红包
        RedRobRecordEntity redRobRecordEntity = redRobRecordService.getByUserIdAndRedRecord(userId, newKey);
        boolean isNotNullByRob = Objects.nonNull(redRobRecordEntity);
        if (isNotNullByRob) {
            logger.error("该用户已经抢多红包；{}", userId);
            return redRobRecordEntity.getAmount();
        }
        // 缓存中是否还有红包
        boolean isCanRob = clickRed(newKey);
        if (!isCanRob) {
            logger.error("红包已经全部抢完！");
            return null;
        }
        // 定义对Redis的操作组件
        ValueOperations valueOperations = redisTemplate.opsForValue();
        // 定义分布式锁
        String lockKey = newKey + userId + "-lock";
        boolean isLock = valueOperations.setIfAbsent(lockKey, newKey);
        redisTemplate.expire(lockKey, 24L, TimeUnit.HOURS);
        // 如果当前线程没有获取到分布式锁
        if (!isLock) {
            return null;
        }
        // 随机弹出一个红包
        Object redValue = redisTemplate.opsForList().rightPop(newKey);
        boolean isNullByRedValue = Objects.isNull(redValue);
        if (isNullByRedValue) {
            return null;
        }
        // 抢到红包 更新缓存与数据库
        String redTotalKey = newKey + ":total";
        synchronized (RedPackageFacadeImpl.class) {
            Object totalObject = valueOperations.get(redTotalKey);
            int currentTotal = totalObject != null ? Integer.parseInt(totalObject.toString()) : 0;
            valueOperations.set(redTotalKey, String.valueOf(currentTotal-1));
        }
        // 格式化金额为：元
        BigDecimal result = new BigDecimal(redValue.toString()).divide(new BigDecimal(100), 2);

        // 与红包相关的信息入库
        RedRobRecordEntity redRobRecordEntityNew = new RedRobRecordEntity();
        redRobRecordEntityNew.setRedPacket(newKey);
        redRobRecordEntityNew.setAmount(new BigDecimal(redValue.toString()));
        redRobRecordEntityNew.setCreateTime(LocalDateTime.now());
        redRobRecordEntityNew.setIsActive(true);
        redRobRecordEntityNew.setUserId(userId);
        redRobRecordService.save(redRobRecordEntityNew);
        // 当前抢到红包的用户设置进缓存
        valueOperations.set(newKey + userId + ":rob", result.toString(), 24L, TimeUnit.HOURS);
        logger.info("当前用户抢到红包了 userId={}, key={}, 金额={}", userId, newKey, result);
        return result;
    }

    /**
     * 判断是否还有红包可以抢
     * @param newKey 红包全局字符串
     * @return 是否有资格
     */
    private boolean clickRed(String newKey) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        String redTotalKey = newKey + ":total";
        Object total = valueOperations.get(redTotalKey);
        return Objects.nonNull(total) && Integer.parseInt(total.toString()) > 0;
    }

    /**
     * 保存与红包相关的信息
     * @param redPackageDto 红包信息对象
     * @param newKey 红包唯一标识
     * @param result 红包列表
     */
    private void saveRedPackageRelatedInfo(RedPackageDto redPackageDto, String newKey, List<Integer> result) {
        // redRecord
        RedRecordEntity redRecordEntity = BeanUtils.convert(redPackageDto, RedRecordEntity::new);
        redRecordEntity.setAmount(BigDecimal.valueOf(redPackageDto.getAmount()));
        redRecordEntity.setRedPacket(newKey);
        redRecordEntity.setCreateTime(LocalDateTime.now());
        redRecordEntity.setIsActive(true);
        redRecordService.save(redRecordEntity);

        // redDetail
        result.forEach(it -> {
            RedDetailEntity redDetailEntity = new RedDetailEntity();
            redDetailEntity.setAmount(BigDecimal.valueOf(it));
            redDetailEntity.setRecordId(redRecordEntity.getId());
            redDetailEntity.setCreateTime(LocalDateTime.now());
            redDetailEntity.setIsActive(true);
            redDetailService.save(redDetailEntity);
        });
    }

    /**
     * 生成随机红包 以分为单位
     * @param amount 总金额
     * @param total 红包个数
     * @return 红包集合
     */
    private List<Integer> delverRedPackage(Integer amount, Integer total) {
        List<Integer> result = new ArrayList<>();
        boolean isNotNull = amount > 0 && total > 0;
        if (isNotNull) {
            int lastAmount = amount;
            int lastTotal = total;
            Random random = new Random();
            for (int i = 0; i < total-1; i++) {
                // 随机生成一个红包 [1, astTotal / lastAmount * 2 - 1）
                int redValue = random.nextInt(lastAmount / lastTotal * 2 - 1) + 1;
                result.add(redValue);
                lastTotal--;
                lastAmount -= redValue;
            }
            result.add(lastTotal);
        }
        return result;
    }
}
